//
//  SSSQLiteManager.swift
//  数据库
//
//  Created by 叶竞泽 on 2017/2/12.
//  Copyright © 2017年 叶竞泽. All rights reserved.
//

import Foundation
import FMDB

/// 最大数据库缓存时间,以秒为单位
private let maxDBCacheTime: TimeInterval = -5 * 24 * 60 * 60

/**
 1.数据库本质上是保存在沙盒中的一个文件,首先需要创建并且打开数据库
    - FMDB使用的是队列Queue - FMDatabaseQueue
 2.创建数据表
 3.增删改查
 
 数据库开发,程序代码几乎都是一致的,区别在SQL
 开发数据库功能的时候,一定要在Navicat中测试SQL的正确性
 
 -- 测试插入数据SQL --
 INSERT OR REPLACE INTO T_Status (statusId, userId, status) VALUES (101, 2, 'bad news');
 
 */

/// SQLite管理器
class SSSQLiteManager {
    
    /// 全局数据库工具访问点
    static let shared = SSSQLiteManager()
    
    /// 数据库队列
    let queue: FMDatabaseQueue
    
    /// 构造函数 - 不允许直接创建实例
    private init() {
        
        // 1.数据库的全路径 - path
        let dbName = "status.db"
        var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        path = (path as NSString).appendingPathComponent(dbName)
        print("数据库的路径是\(path)")
        
        // 2.创建数据库队列 - 同时创建数据库文件并打开连接
        queue = FMDatabaseQueue(path: path)
        createTabel()
        
        // 注册通知 - 监听应用程序进入后台
        NotificationCenter.default.addObserver(self, selector: #selector(clearDBCache), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // MARK: - 监听方法
    
    /// 清理数据缓存
    /// SQLite数据库不断的增加数据,数据库文件越来越大,但是如果删除了数据,数据库的大小不会变小
    /// 如果想让SQLite数据库文件变小
    /// 1: 复制副本 2: 新建一个空数据库文件 3:自己编写SQL,从old数据库读出数据,写入新的数据库 4:删除老的数据库
    @objc fileprivate func clearDBCache() {
        
        let dateString = Date.ss_dateString(withDate: maxDBCacheTime)
        
        // 1.创建SQL
        let sql = "DELETE FROM T_Status WHERE createTime < ?"
        
        // 2.执行SQL
        queue.inDatabase { (db) in
            
            if db?.executeUpdate(sql, withArgumentsIn: [dateString]) == true {
                print("删除了\(db?.changes())条记录")
            }
        }
        
    }
    
}

// MARK: - 微博数据操作
extension SSSQLiteManager {
    
    /// 从数据库加载微博数据
    ///
    /// - Parameters:
    ///   - userId: 当前登录的用户id
    ///   - since_id: 返回比since_id大的微博
    ///   - max_id: 返回比max_id小的微博
    /// - Returns: 微博数组,将数据库中的二进制数据反序列化,生成字典
    func loadStatus(userId: String, since_id: Int64 = 0, max_id: Int64 = 0) -> [[String: Any]] {
        
//        -- 上拉和下拉都是针对同一个statusId进行判断
//        SELECT statusId, userId, status FROM T_Status
//        WHERE userId = 1
//        AND statusId > 110
//        ORDER BY statusId DESC
//        LIMIT 20;
        
        // 1.准备SQL
        var sql = "SELECT statusId, userId, status FROM T_Status \n"
        
        sql += "WHERE userId = \(userId) \n"
        
        
        if since_id > 0 {
            
            // 下拉加载
            sql += "AND statusId > \(since_id) \n"
            
        } else if max_id > 0 {
            
            // 上拉加载
            sql += "AND statusId < \(max_id) \n"
            
        }
        
        sql += "ORDER BY statusId DESC LIMIT 20;"
        
        // 2.执行SQL
        
        let results = execRecordSet(sql: sql)
        
        // 3.遍历数组,将数组中的status反序列化,生成字典数组
        
        var array = [[String: Any]]()
        
        for dict in results {
            
            // 反序列化
            guard let data = dict["status"] as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
                continue
            }
            
            // 追加到数组
            if let temp = json {
                array.append(temp)
            }
        }
        
        return array
    }
    
    /**
     从网络加载结束后,返回的是微博的字典数组,每一个字典对应的是一条完整的微博记录,包含微博的代号,不包含当前登录的用户id
    */
    /// 新增或者修改微博数据,微博数据在刷新的时候可能会出现重叠
    ///
    /// - Parameters:
    ///   - userId: 当前登录的用户id
    ///   - array: 从网络获取的微博字典数组
    func updateStatus(userId: String, array: [[String: Any]]) {
        
        // 1.准备SQL
        /**
         statusId: 要保存的微博代号
         userId: 当前登录的微博用户的id
         status: 完整的微博字典的json二进制数据
        */
        let sql = "INSERT OR REPLACE INTO T_Status (statusId, userId, status) VALUES (?, ?, ?);"
        
        // 2.执行SQL
        queue.inTransaction { (db, rollback) in
            
            // 遍历数组,逐条插入微博数据
            for dict in array {
                
                // 从字典获取微博id,将字典序列化成二进制数据
                guard let statusId = dict["idstr"] as? String, let status = try? JSONSerialization.data(withJSONObject: dict, options: []) else {
                    continue
                }
                
                // 执行SQL
                if db?.executeUpdate(sql, withArgumentsIn: [statusId, userId, status]) == false {
                    
                    // 失败回滚
                    rollback?.pointee = true
                    
                    break
                }
                
                print("更新成功")
            }
        }
    }
    
    /// 执行SQL,返回字典数组
    ///
    /// - Parameter sql: SQL语句
    /// - Returns: 字典数组
    fileprivate func execRecordSet(sql: String) -> [[String: Any]] {
        
        var array = [[String: Any]]()
        
        // 执行SQL - 查询不会修改数据,所以不用在事务里执行
        // 事务的目的是保证数据有效性,一旦失败,可回滚到初始状态
        queue.inDatabase { (db) in
            
            guard let results = db?.executeQuery(sql, withArgumentsIn: []) else {
                return
            }
            
            // 逐行遍历结果集合
            while results.next() {
                
                // 1.获取列数
                let colCount = results.columnCount()
                // 2.遍历所有列
                for i in 0 ..< colCount {
                    
                    // 取列名 - key , 取值 - value
                    guard let colName = results.columnName(for: i), let value = results.object(forColumnIndex: i) else {
                        continue
                    }
                    
                    // 追加结果
                    array.append([colName: value])
                }
            }
        }
        
        return array
    }
}

// MARK: - 创建数据表及其它私有方法
fileprivate extension SSSQLiteManager {
    
    /// 创建数据表
    func createTabel() {
        
        // 1.准备SQL
        guard let path = Bundle.main.path(forResource: "status.sql", ofType: nil), let sql = try? String(contentsOfFile: path) else {
            return
        }
        
        // 2.执行SQL - FMDB内部是串行队列,同步执行的函数,保证同一时间,只有一个任务操作数据库,从而保证数据库的读写安全
        queue.inDatabase { (db) in
            if db?.executeStatements(sql) == true {
                print("创建表成功")
            } else {
                print("创建表失败")
            }
        }
        
        print("over")
    }
    
}
